using System;
using System.IO;
using System.Xml;
using System.Xml.Xsl;

using OR.Reuse.Persistence;

using OR.Lazarus.Config;
using OR.Lazarus.Utility;

using ConfigPacket = OR.Lazarus.Business.Entities.Config;
using ManifestPacket = OR.Lazarus.Business.Entities.Manifest;

namespace OR.Lazarus.Business
{
	/// <summary>
	/// Summary description for CodeGenerator.
	/// </summary>
	public class CodeGenerator<COMPILED>
      where COMPILED : IProject, new()
	{
		protected ConfigPacket.ConfigDetail mConfig;
      protected COMPILED mCompiled;
		protected ManifestPacket.ManifestDetail mManifest;
		protected long mOutputLength;

		protected int mFilesToGenerate;
		protected int mFilesGenerated;

		protected string mOutputPath;

		protected event GenerateEventHandler mGenerateFile;
		protected event TierGenerateEventHandler mBeforeGenerateTier;
		protected event TierGenerateEventHandler mAfterGenerateTier;
		protected event EventHandler mBeforeGenerateCompiled;
		protected event EventHandler mAfterGenerateCompiled;
		protected event EventHandler mBeforeGenerateManifest;
		protected event EventHandler mAfterGenerateManifest;

		public event GenerateEventHandler GenerateFile
		{
			add
			{
				mGenerateFile += value;
			}
			remove
			{
				mGenerateFile -= value;
			}
		}

		public event EventHandler BeforeGenerateCompiled
		{
			add
			{
				mBeforeGenerateCompiled += value;
			}
			remove
			{
				mBeforeGenerateCompiled -= value;
			}
		}

		public event EventHandler AfterGenerateCompiled
		{
			add
			{
				mAfterGenerateCompiled += value;
			}
			remove
			{
				mAfterGenerateCompiled -= value;
			}
		}

		public event EventHandler BeforeGenerateManifest
		{
			add
			{
				mBeforeGenerateManifest += value;
			}
			remove
			{
				mBeforeGenerateManifest -= value;
			}
		}

		public event EventHandler AfterGenerateManifest
		{
			add
			{
				mAfterGenerateManifest += value;
			}
			remove
			{
				mAfterGenerateManifest -= value;
			}
		}

		public event TierGenerateEventHandler BeforeGenerateTier
		{
			add
			{
				mBeforeGenerateTier += value;
			}
			remove
			{
				mBeforeGenerateTier -= value;
			}
		}

		public event TierGenerateEventHandler AfterGenerateTier
		{
			add
			{
				mAfterGenerateTier += value;
			}
			remove
			{
				mAfterGenerateTier -= value;
			}
		}

      public event CompileEventHandler CompiledModelGenerated
      {
         add
         {
            mCompiledModelGenerated += value;
         }
         remove
         {
            mCompiledModelGenerated -= value;
         }
      }

      public event CompileEventHandler mCompiledModelGenerated;

		public ConfigPacket.ConfigDetail Config
		{
			get
			{
				return ( mConfig );
			}
			set
			{
				mConfig = value;
			}
		}

		public COMPILED Compiled
		{
			get
			{
				return ( mCompiled );
			}
			set
			{
				mCompiled = value;
			}
		}

		public ManifestPacket.ManifestDetail Manifest
		{
			get
			{
				return ( mManifest );
			}
			set
			{
				mManifest = value;
			}
		}

		public string OutputPath
		{
			get
			{
				return ( mOutputPath );
			}
			set
			{
				mOutputPath = value;
			}
		}

		public long OutputLength
		{
			get
			{
				return ( mOutputLength );
			}
			set
			{
				mOutputLength = value;
			}
		}

		public int GetGenerateCount()
		{
			int vReturn = 0;
			XmlDocument v_xmlCompiled = mCompiled.SaveXmlDocument();

			foreach ( ConfigPacket.TierDetail vTier in mConfig.Tiers )
			{
				foreach ( ConfigPacket.TransformDetail vTransform in vTier.Transforms )
				{
					if ( String.IsNullOrEmpty( vTransform.Filter ) )
					{
						vReturn++;
					}
					else
					{
						vReturn += v_xmlCompiled.SelectNodes( vTransform.Filter, PersistenceConfig.Namespaces ).Count;
					}
				}
			}

			return ( vReturn );
		}

		public void Generate()
		{
			this.OnValidate();

			this.OnGenerate();
		}

		public virtual void Initialize()
		{
			mOutputPath = Path.Combine( LazarusConfig.OutputPath, mCompiled.GetRelativePath( "/", false, false ) );
		}

		protected virtual void OnGenerate()
		{
			this.OnInitializeManifest();

			XslCompiledTransform v_XslCompiledTransform = new XslCompiledTransform();
			XmlDocument v_xmlCompiled = mCompiled.SaveXmlDocument();

			long lOutputLength = 0;

			string szOutputFile = "";

			lOutputLength = 0;

			try
			{
				DirectoryUtility.VerifyPath( mOutputPath );

				// If the filename is specified, output the Compiled XML file.
				if ( !String.IsNullOrEmpty( mConfig.CompiledOutput ) )
				{
					szOutputFile = Path.Combine( mOutputPath, mConfig.CompiledOutput );

					FileUtility.WriteFile( szOutputFile, v_xmlCompiled );

               this.OnCompiledModelGenerated( new CompileEventArgs( szOutputFile, v_xmlCompiled.OuterXml.Length ) );
				}

				this.OnBeforeGenerateCompiled( new EventArgs() );

				this.ProcessTiers( v_xmlCompiled, "compiled" );

				this.OnAfterGenerateCompiled( new EventArgs() );

				this.OnBeforeGenerateManifest( new EventArgs() );

				this.ProcessTiers( mManifest.SaveXmlDocument(), "manifest" );

				this.OnAfterGenerateManifest( new EventArgs() );

				szOutputFile = Path.Combine( mOutputPath, "manifest.xml" );

				FileUtility.WriteFile( szOutputFile, mManifest.SaveXmlDocument() );
			}
			catch ( XsltCompileException e )
			{
				throw ( new Exception( e.InnerException.Message ) );
			}
			catch ( Exception e )
			{
				throw ( e );
			}

			mOutputLength = lOutputLength;
		}

		protected virtual void ProcessTiers( XmlDocument pSource, string pMode )
		{
			XslCompiledTransform v_XslCompiledTransform = new XslCompiledTransform();
			GenerateEventArgs vGenerateArgs = null;
			ManifestPacket.FileDetail vManifestFile = null;

			long lFileOutputLength = 0;
			long lOutputLength = 0;

			string vSystemPath = String.Empty;
			string vInputFile = String.Empty;
			string vOutputPath = String.Empty;
			string vOutputFile = String.Empty;
			string vFilename = String.Empty;
			string vRelativePath = String.Empty;
			string vRelativeFile = String.Empty;
			string vManifestFileName = String.Empty;
			string vConceptName = String.Empty;
			FileRegionMerger vMerger = new FileRegionMerger();
			Stream vNewFile = null;
			Stream vExistingFile = null;
			Stream vMergedFile = null;

			string vSuffix = String.Empty;

			// Loop through the Tier enumeration
			foreach ( ConfigPacket.TierDetail vTier in mConfig.Tiers )
			{
				if ( vTier.Transforms.ListByType( pMode ).Count != 0 )
				{
					this.OnBeforeGenerateTier( new TierGenerateEventArgs( vTier ) );

					vSystemPath = Path.Combine( LazarusConfig.TransformPath, vTier.Transforms.Library );

					// Copy INCLUDE files first.
					foreach ( ConfigPacket.IncludeDetail vInclude in vTier.Includes )
					{
						vInputFile = Path.Combine( vSystemPath, vInclude.Source );
						vOutputPath = Path.Combine( mOutputPath, vTier.Folder );
						vOutputFile = Path.Combine( vOutputPath, vInclude.Source );

						DirectoryUtility.VerifyPath( vOutputPath );

						if ( File.Exists( vOutputFile ) )
						{
							File.SetAttributes( vOutputFile, FileAttributes.Normal );

							File.Delete( vOutputFile );
						}

						File.Copy( vInputFile, vOutputFile, true );
					}

					foreach ( ConfigPacket.TransformDetail vTransform in vTier.Transforms.ListByType( pMode ) )
					{
						vInputFile = Path.Combine( vSystemPath, vTransform.Source );

						try
						{
							v_XslCompiledTransform.Load( vInputFile );
						}
						catch ( Exception ex )
						{
							throw new ApplicationException( "Loading of XSLT [file://" + vInputFile + "] failed: " + ex.Message, ex );
						}

						vRelativePath = Path.Combine( vTier.Folder, ( vTransform.Folder != null ? vTransform.Folder + "\\" : String.Empty ) );

						DirectoryUtility.VerifyPath( Path.Combine( mOutputPath, vRelativePath ) );

						if ( String.IsNullOrEmpty( vTransform.Filter ) )
						{
							vConceptName = vTransform.Output.Substring( 0, vTransform.Output.IndexOf( "." ) );
							vFilename = vTransform.Output;
							vRelativeFile = Path.Combine( vRelativePath, vTransform.Output );
							vManifestFileName = vRelativeFile;
							vOutputFile = Path.Combine( mOutputPath, vRelativeFile );

							try
							{
								lFileOutputLength = FileUtility.WriteFile( vOutputFile, pSource, v_XslCompiledTransform );
								lOutputLength += lFileOutputLength;

									vGenerateArgs = new GenerateEventArgs();

									vGenerateArgs.Filename = vOutputFile;
									vGenerateArgs.FileSize = lFileOutputLength;
									vGenerateArgs.XslName = vInputFile;

									this.OnGenerateFile( vGenerateArgs );
							}
							catch ( Exception ex )
							{
								throw ( ex );
							}

							vManifestFile = new ManifestPacket.FileDetail();
							vManifestFile.Name = vFilename;
							vManifestFile.RelativePath = vRelativePath;
							vManifestFile.Size = Convert.ToInt32( lFileOutputLength );
							vManifestFile.Type = vTransform.OutputType;
							vManifestFile.ConceptName = vConceptName;

							mManifest.Files.Add( vManifestFile );
						}
						else
						{
							XmlNodeList nodesItems = pSource.SelectNodes( vTransform.Filter, PersistenceConfig.Namespaces );

							foreach ( XmlElement v_elContext in nodesItems )
							{
								vSuffix = v_elContext.GetAttribute( "suffix" );

								v_elContext.SetAttribute( "compiling", "true" );

								vConceptName = v_elContext.GetAttribute( "filename" ) + vTransform.Suffix + vSuffix;
								vFilename = vTransform.Prefix + vConceptName + "." + vTransform.Output;
								vRelativeFile = Path.Combine( vRelativePath, vFilename );
								vManifestFileName = vRelativeFile;
								vOutputFile = Path.Combine( mOutputPath, vRelativeFile );

								try
								{
									// If the file already exists, check the overwrite mode.
									if ( ( vTransform.Overwrite != ConfigPacket.ConfigDetail.OVERWRITE_ALWAYS ) && File.Exists( vOutputFile ) )
									{
										switch ( vTransform.Overwrite )
										{
											case ConfigPacket.ConfigDetail.OVERWRITE_REGIONS:
												{
													vNewFile = new MemoryStream();

													FileUtility.WriteStream( vNewFile, v_elContext.CreateNavigator(), v_XslCompiledTransform );

													vExistingFile = FileUtility.ReadFile( vOutputFile );

													if ( vExistingFile.Length > 0 )
													{
														vMergedFile = vMerger.Merge( vNewFile, vExistingFile );

														vExistingFile.Flush();
														vExistingFile.Close();

														lFileOutputLength = vMergedFile.Length;
														lOutputLength += lFileOutputLength;

														FileUtility.WriteFile( vOutputFile, vMergedFile );
													}
													else
													{
														vExistingFile.Flush();
														vExistingFile.Close();

														lFileOutputLength = vNewFile.Length;
														lOutputLength += lFileOutputLength;

														FileUtility.WriteFile( vOutputFile, vNewFile );
													}

													vGenerateArgs = new GenerateEventArgs();

													vGenerateArgs.Filename = vOutputFile;
													vGenerateArgs.FileSize = Convert.ToInt32( lFileOutputLength );
													vGenerateArgs.XslName = vInputFile;

													this.OnGenerateFile( vGenerateArgs );

													break;
												}
											case ConfigPacket.ConfigDetail.OVERWRITE_NEVER:
												{
													lFileOutputLength = 0;

													break;
												}
										}
									}
									else
									{
										lFileOutputLength = FileUtility.WriteFile( vOutputFile, v_elContext.CreateNavigator(), v_XslCompiledTransform );
										lOutputLength += lFileOutputLength;

										vGenerateArgs = new GenerateEventArgs();

										vGenerateArgs.Filename = vOutputFile;
										vGenerateArgs.FileSize = Convert.ToInt32( lFileOutputLength );
										vGenerateArgs.XslName = vInputFile;

										this.OnGenerateFile( vGenerateArgs );
									}
								}
								catch ( Exception ex )
								{
									throw ( ex );
								}

								v_elContext.RemoveAttribute( "compiling" );

								vManifestFile = new ManifestPacket.FileDetail();
								vManifestFile.Name = vFilename;
								vManifestFile.RelativePath = vRelativePath;
								vManifestFile.Size = Convert.ToInt32( lFileOutputLength );
								vManifestFile.Type = vTransform.OutputType;
								vManifestFile.ConceptName = vConceptName;

								mManifest.Files.Add( vManifestFile );
							}
						}
					}

					this.OnAfterGenerateTier( new TierGenerateEventArgs( vTier ) );
				}
			}
		}

		private void OnGenerateFile( GenerateEventArgs e )
		{
			mFilesGenerated++;

			if ( mGenerateFile != null )
			{
				mGenerateFile( this, e );
			}
			
			LazarusConfig.LogViewerManager.AddGenerationLog( e );
		}

		protected virtual void OnBeforeGenerateTier( TierGenerateEventArgs e )
		{
			if ( mBeforeGenerateTier != null )
			{
				mBeforeGenerateTier( this, e );
			}

			LazarusConfig.LogViewerManager.AddTierGenerationStartedMessage( e );
		}

      protected virtual void OnCompiledModelGenerated( CompileEventArgs e )
      {
         if ( mCompiledModelGenerated != null )
         {
            mCompiledModelGenerated( this, e );
         }

			LazarusConfig.LogViewerManager.AddCompileFileGenerated( e.Filename, e.FileSize );
		}

		protected virtual void OnAfterGenerateTier( TierGenerateEventArgs e )
		{
			if ( mAfterGenerateTier != null )
			{
				mAfterGenerateTier( this, e );
			}

			LazarusConfig.LogViewerManager.AddTierGenerationCompletedMessage( e );
		}

		protected virtual void OnBeforeGenerateCompiled( EventArgs e )
		{
			if ( mBeforeGenerateCompiled != null )
			{
				mBeforeGenerateCompiled( this, e );
			}
		}

		protected virtual void OnAfterGenerateCompiled( EventArgs e )
		{
			if ( mAfterGenerateCompiled != null )
			{
				mAfterGenerateCompiled( this, e );
			}
		}

		protected virtual void OnBeforeGenerateManifest( EventArgs e )
		{
			if ( mBeforeGenerateManifest != null )
			{
				mBeforeGenerateManifest( this, e );
			}
		}

		protected virtual void OnAfterGenerateManifest( EventArgs e )
		{
			if ( mAfterGenerateManifest != null )
			{
				mAfterGenerateManifest( this, e );
			}
		}

		protected virtual void OnInitializeManifest()
		{
			mManifest.ProjectName = mCompiled.Name;

			mManifest.OutputPath = mOutputPath;
		}

		protected virtual void OnValidate()
		{
			if ( mConfig == null )
			{
				throw new Exception( "CodeGenerator.OnValidate() failed: No Config File has been loaded." );
			}

			if ( mCompiled == null )
			{
				throw new Exception( "CodeGenerator.OnValidate() failed: No Compiled File has been loaded." );
			}

			if ( mManifest == null )
			{
				mManifest = new ManifestPacket.ManifestDetail();
			}
		}

		public CodeGenerator()
		{
			mManifest = new ManifestPacket.ManifestDetail();
			mConfig = new ConfigPacket.ConfigDetail();
		}

		public int FilesToGenerate
		{
			get
			{
				return ( mFilesToGenerate );
			}
			set
			{
				mFilesToGenerate = value;
			}
		}

		public int FilesGenerated
		{
			get
			{
				return ( mFilesGenerated );
			}
			set
			{
				mFilesGenerated = value;
			}
		}
	}
}
